using System;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using Nakio.Framework.Exceptions;
using Nakio.Framework.Interfaces;

namespace Nakio.Framework.Communication
{
    /// <summary>
    /// Command binder for serialization
    /// </summary>
    internal class ClientAssemblyBinder : SerializationBinder
    {
        private readonly IClientCallback _clientCallback;
        private readonly EncryptToken _encryptToken;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="clientCallback">The client wich is trying to deserialize the command</param>
        /// <param name="encryptToken">The Encrypt token of the client</param>
        public ClientAssemblyBinder(IClientCallback clientCallback, EncryptToken encryptToken)
        {
            _clientCallback = clientCallback;
            _encryptToken = encryptToken;
        }

        /// <summary>
        /// Trys to bind the Command to a type
        /// </summary>
        /// <param name="assemblyName">The assembly name to bind to</param>
        /// <param name="typeName">The type of the assembly</param>
        /// <returns>A type to bind to</returns>
        public override Type BindToType(string assemblyName, string typeName)
        {
            Type typeToDeserialize = null;
            var assemblies = AppDomain.CurrentDomain.GetAssemblies();
            foreach (Assembly assembly in assemblies)
            {
                if (assembly.FullName == assemblyName)
                {
                    typeToDeserialize = assembly.GetType(typeName);
                    break;
                }
            }

            if (typeToDeserialize == null)
            {
                var command = new MissingAssemblyCommand(assemblyName);
                var encryptedCommand = new EncryptedCommand(_encryptToken, command);
                var assembly = (byte[]) _clientCallback.ExecuteClientReturnCommand(encryptedCommand);
                Assembly.Load(assembly);
                
                assemblies = AppDomain.CurrentDomain.GetAssemblies();
                try
                {
                    typeToDeserialize = assemblies.First(a => a.FullName == assemblyName).GetType(typeName);
                }
                catch (Exception ex)
                {

                    throw new CannotFindBindAssemblyException(ex);
                }                
            }
            return typeToDeserialize;
        }
    }
}